Zero-shot time series forecasting with Chronos using Amazon Bedrock and ClickHouse

Architecture diagram showing Chronos on Amazon Bedrock integrated with ClickHouse in AWS Lambda

1. Overview

The emergence of large language models (LLMs) with zero-shot generalization capabilities in sequence modelling tasks has led to the development of time series foundation models (TSFMs) based on LLM architectures. By representing time series as sequences of tokens, TSFMs can leverage LLMs’ capability to extrapolate future patterns from the context data. TSFMs eliminate the traditional need for domain-specific model development, allowing organizations to deploy accurate time series solutions faster.

In this post, we will focus on Chronos [1], a family of TSFMs for probabilistic time series forecasting developed by Amazon. In contrast to other TSFMs, that rely on LLMs pre-trained on text, Chronos models are trained from scratch on a large collection of time series datasets. Moreover, unlike other TSFMs, which require fine-tuning on in-domain data, Chronos models generate accurate zero-shot forecasts, without any task-specific adjustments.

Recently, the Chronos family of TSFMs has been extended with Chronos-Bolt [2], a faster, more accurate, and more memory-efficient Chronos model that can also be used on CPU. Chronos-Bolt is available in AutoGluon-TimeSeries, Amazon SageMaker JumpStart and Amazon Bedrock.

In the rest of this post, we will walk through a practical example of using Chronos-Bolt with time series data stored in ClickHouse. We will create a Bedrock endpoint, then build a Lambda function that invokes the Bedrock endpoint with context data queried from ClickHouse and returns the forecasts.

2. Solution

In this particular example, we will work with the 15-minute time series of the Italian electricity system’s total demand, which we downloaded from Terna’s data portal and stored in a table in ClickHouse which we called total_load_data. However, as we are performing zero-shot forecasting without domain-specific tuning, this solution can be applied to any other time series.

Last 10 rows of total_load_data ClickHouse table

Figure 1: Last 10 rows of total_load_data ClickHouse table.

Note

To be able to run the code provided in the rest of this section, you will need to have Boto3 and the AWS-CLI installed on your machine. You will also need to update several variables in the code to reflect your AWS configuration - such as your AWS account number, region, service roles, etc.

2.1 Create the Bedrock endpoint

We start by deploying Chronos-Bolt to a Bedrock endpoint hosted on a CPU EC2 instance. This can be done using Boto3 as in the code below, with the AWS-CLI, or directly from the Bedrock console.

import boto3

# Create the Bedrock client
bedrock_client = boto3.client("bedrock")

# Create the Bedrock endpoint
response = bedrock_client.create_marketplace_model_endpoint(
    modelSourceIdentifier="<bedrock-marketplace-arn>",
    endpointConfig={
        "sageMaker": {
            "initialInstanceCount": 1,
            "instanceType": "ml.m5.4xlarge",
            "executionRole": "<bedrock-execution-role>"
        }
    },
    endpointName="chronos-bedrock-endpoint",
    acceptEula=True,
)

# Get the Bedrock endpoint ARN
bedrock_endpoint_arn = response["marketplaceModelEndpoint"]["endpointArn"]

Important

Remember to delete the endpoint when is no longer needed to avoid unexpected charges. This can be done using Boto3 as in the code below, with the AWS-CLI, or directly from the Bedrock console.

# Delete the Bedrock endpoint
response = bedrock_client.delete_marketplace_model_endpoint(
    endpointArn=bedrock_endpoint_arn
)

2.2 Create the Lambda function for invoking the Bedrock endpoint with ClickHouse data

We now build a Lambda function for invoking the Bedrock endpoint with time series data stored in ClickHouse.

The Lambda function connects to ClickHouse using ClickHouse Connect and loads the context data using the query_df method, which returns the query output in a Pandas DataFrame. After that, the Lambda function invokes the Bedrock endpoint with the context data.

The Bedrock endpoint response includes the predicted mean and the predicted quantiles of the time series at each future time step, which the Lambda function returns to the user in JSON format together with the corresponding timestamps.

2.2.1 Create the Docker image

In order to create the Lambda function’s Docker image in Elastic Container Registry (ECR), we need the following files:

  • app.py: The Python code of the Lambda function.

  • requirements.txt: The list of dependencies that need to be installed in the Docker container.

  • Dockerfile: The file containing the instructions to build the Docker image.

2.2.1.1 app.py

The app.py Python script with the entry point of the Lambda function is reported below.

Note

For simplicity, in this example we have included the ClickHouse database credentials directly in the code. In practice, we recommend using AWS Secrets Manager to securely store, manage, and retrieve credentials.

import json
import boto3
import pandas as pd
import clickhouse_connect

def handler(event, context):
    """
    Generate zero-shot forecasts with Chronos-Bolt (Base) Amazon Bedrock endpoint using data stored in ClickHouse.

    Parameters:
    ========================================================================================================
    event: dict.
        A dictionary with the following keys:

        initialization_timestamp: str.
            The initialization timestamp of the forecasts, in ISO format (YYYY-MM-DD HH:mm:ss).

        frequency: int.
            The frequency of the time series, in minutes.

        context_length: int.
            The number of past time steps to use as context.

        prediction_length: int.
            The number of future time steps to predict.

        quantile_levels: list of float.
            The quantiles to be predicted at each future time step.

    context: AWS Lambda context object, see https://docs.aws.amazon.com/lambda/latest/dg/python-context.html.
    """
    # Create the ClickHouse client
    clickhouse_client = clickhouse_connect.get_client(
        host="<clickhouse-host>",
        user="<clickhouse-user>",
        password="<clickhouse-password>",
        secure=True
    )

    # Load the input data from ClickHouse
    df = clickhouse_client.query_df(
        f"""
            select
                timestamp,
                total_load
            from
                total_load_data
            where
                timestamp < toDateTime('{event['initialization_timestamp']}')
            and
                timestamp >= toDateTime('{event['initialization_timestamp']}') - INTERVAL {int(event['frequency']) * int(event['context_length'])} MINUTES
            order by
                timestamp asc
        """
    )

    # Create the Bedrock client
    bedrock_runtime_client = boto3.client("bedrock-runtime")

    # Invoke the Bedrock endpoint with the ClickHouse data
    response = bedrock_runtime_client.invoke_model(
        modelId="<bedrock-endpoint-arn>",
        body=json.dumps({
            "inputs": [{
                "target": df["total_load"].values.tolist(),
            }],
            "parameters": {
                "prediction_length": event["prediction_length"],
                "quantile_levels": event["quantile_levels"],
            }
        })
    )

    # Extract the forecasts
    predictions = json.loads(response["body"].read()).get("predictions")[0]

    # Add the timestamps to the forecasts
    predictions = {
        "timestamp": [
            x.strftime("%Y-%m-%d %H:%M:%S")
            for x in pd.date_range(
                start=event["initialization_timestamp"],
                periods=event["prediction_length"],
                freq=f"{event['frequency']}min",
            )
        ]
    } | predictions

    # Return the forecasts
    return {
        "statusCode": 200,
        "body": json.dumps(predictions)
    }

The handler function has two arguments:

  • event: The input payload with the request parameters.

  • context: The runtime information about the invocation.

In this case, the event object is expected to include the following fields:

  • "initialization_timestamp": The first timestamp for which the forecasts should be generated.

  • "frequency": The frequency of the time series, in number of minutes.

  • "context_length": The number past time series values (prior to initialization_timestamp) to use as context.

  • "prediction_length": The number of future time series values (on and after initialization_timestamp) to predict.

  • "quantile_levels": The quantiles to be predicted at each future time step.

The context object is automatically generated at runtime and does not need to be provided.

2.2.1.2 requirements.txt

The requirements.txt file with the list of dependencies is as follows:

boto3==1.34.84
clickhouse_connect==0.8.18
pandas==2.3.1

2.2.1.3 Dockerfile

The standard Dockerfile using the Python 3.12 AWS base image for Lambda is also provided for reference:

FROM amazon/aws-lambda-python:3.12

COPY requirements.txt  .

RUN pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"

COPY app.py ${LAMBDA_TASK_ROOT}

CMD ["app.handler"]

2.2.2 Build the Docker image and push it to ECR

When all the files are ready, we can build the Docker image and push it to ECR with the AWS-CLI as shown in the build_and_push.sh script below.

aws_account_id="<aws-account-id>"
region="<ecr-repository-region>"
algorithm_name="<ecr-repository-name>"

aws ecr get-login-password --region $region | docker login --username AWS --password-stdin $aws_account_id.dkr.ecr.$region.amazonaws.com

aws ecr describe-repositories --repository-names ${algorithm_name} || aws ecr create-repository --repository-name ${algorithm_name}

docker build -t $algorithm_name .

docker tag $algorithm_name:latest $aws_account_id.dkr.ecr.$region.amazonaws.com/$algorithm_name:latest

docker push $aws_account_id.dkr.ecr.$region.amazonaws.com/$algorithm_name:latest

2.2.3 Create the Lambda function from the Docker image in ECR

After the Docker image has been pushed to ECR, we can create the Lambda function using Boto3 as in the code below, with the AWS-CLI, or directly from the Lambda console.

import boto3

# Create the Lambda client
lambda_client = boto3.client("lambda")

# Create the Lambda function
response = lambda_client.create_function(
    FunctionName="chronos-lambda-function",
    PackageType="Image",
    Code={
        "ImageUri": "<ecr-image-uri>"
    },
    Role="<lambda-execution-role>",
    Timeout=900,
    MemorySize=128,
    Publish=True,
)

2.3 Invoke the Lambda function and generate the forecasts

After the Lambda function has been created, we can invoke it to generate the forecasts. The code below defines a Python function which invokes the Lambda function with the inputs discussed in the previous section and casts the Lambda function’s JSON output to Pandas DataFrame.

import io
import json
import boto3
import pandas as pd

def invoke_lambda_function(
    initialization_timestamp,
    frequency,
    context_length,
    prediction_length,
    quantile_levels,
    function_name
):
    """
    Invoke the Lambda function that generates zero-shot forecasts with Chronos-Bolt (Base)
    Amazon Bedrock endpoint using data stored in ClickHouse.

    Parameters:
    ========================================================================================================
    initialization_timestamp: str.
        The initialization timestamp of the forecasts, in ISO format (YYYY-MM-DD HH:mm:ss).

    frequency: int.
        The frequency of the time series, in minutes.

    context_length: int.
        The number of past time steps to use as context.

    prediction_length: int.
        The number of future time steps to predict.

    quantile_levels: list of float.
        The quantiles to be predicted at each future time step.

    function_name: str.
        The name of the Lambda function.
    """
    # Create the Lambda client
    lambda_client = boto3.client("lambda")

    # Invoke the Lambda function
    response = lambda_client.invoke(
        FunctionName=function_name,
        Payload=json.dumps({
            "initialization_timestamp": initialization_timestamp,
            "frequency": frequency,
            "prediction_length": prediction_length,
            "context_length": context_length,
            "quantile_levels": quantile_levels
        })
    )

    # Extract the forecasts in a data frame
    predictions = pd.read_json(io.StringIO(json.loads(response["Payload"].read())["body"]))

    # Return the forecasts
    return predictions

Next, we make two invocations: the first time we request the forecasts over a past time window for which historical data is already available, which allows us to assess how close the forecasts are to the actual data, while the second time we request the forecasts over a future time window for which the data is not yet available. In both cases, we use a 3-week context window to generate 1-day-ahead forecasts.

# Define the Lambda function name and input parameters
frequency = 15
context_length = 24 * 4 * 7 * 3
prediction_length = 24 * 4
quantile_levels = [0.1, 0.5, 0.9]
function_name = "chronos-lambda-function"
# Generate the forecasts over a past time window
predictions = invoke_lambda_function(
    initialization_timestamp="2025-08-17 00:00:00",
    frequency=frequency,
    context_length=context_length,
    prediction_length=prediction_length,
    quantile_levels=quantile_levels,
    function_name=function_name
)
Last 10 rows of Pandas DataFrame with Amazon Chronos forecasts of the Italian electricity system's total demand for August 17, 2025

Figure 2: Last 10 rows of predictions DataFrame.

# Generate the forecasts over a future time window
forecasts = invoke_lambda_function(
    initialization_timestamp="2025-08-18 00:00:00",
    frequency=frequency,
    context_length=context_length,
    prediction_length=prediction_length,
    quantile_levels=quantile_levels,
    function_name=function_name
)
Last 10 rows of Pandas DataFrame with Amazon Chronos forecasts of the Italian electricity system's total demand for August 18, 2025

Figure 3: Last 10 rows of forecasts DataFrame.

2.4 Compare the forecasts to the historical data stored in ClickHouse

Now that the forecasts have been generated, we can compare them to the historical data stored in ClickHouse. We again use ClickHouse Connect to query the database and retrieve the results directly into a Pandas DataFrame.

import clickhouse_connect

# Create the ClickHouse client
clickhouse_client = clickhouse_connect.get_client(
    host="<clickhouse-host>",
    user="<clickhouse-user>",
    password="<clickhouse-password>",
    secure=True
)

# Load the historical data from ClickHouse
df = clickhouse_client.query_df(
    """
    select
        timestamp,
        total_load
    from
        total_load_data
    where
        timestamp >= toDateTime('2025-08-18 23:45:00') - INTERVAL 14 DAYS
    order by
        timestamp asc
    """
)

# Outer join the historical data with the model outputs
output = pd.merge(
    left=df,
    right=pd.concat([predictions, forecasts], axis=0),
    on="timestamp",
    how="outer"
)

The results show that the forecasts are closely aligned with the actual data, demonstrating the model’s ability to generalize effectively in a zero-shot setting. Despite a holiday occurring on the last Friday of the context window, the model produces accurate forecasts for the subsequent Sunday and correctly anticipates an increase in energy demand on the following Monday, highlighting its strength in capturing complex temporal patterns.

Line chart of Amazon Chronos zero-shot probabilistic forecasts of the Italian electricity system's total demand for August 17-18, 2025 against historical data

Figure 4: Chronos-Bolt forecasts against historical total load data.

You can download the full code from our GitHub repository.

References

[1] Ansari, A.F., Stella, L., Turkmen, C., Zhang, X., Mercado, P., Shen, H., Shchur, O., Rangapuram, S.S., Arango, S.P., Kapoor, S. and Zschiegner, J., (2024). Chronos: Learning the language of time series. arXiv preprint, doi: 10.48550/arXiv.2403.07815.

[2] Ansari, A.F., Turkmen, C., Shchur, O., and Stella, L. (2024). Fast and accurate zero-shot forecasting with Chronos-Bolt and AutoGluon. AWS Blogs - Artificial Intelligence.